<?php
///////////////////////////////////////////////////////////////////////////////////////////////////////
//  这个文件是 JCAT PHP框架的一部，该项目和此文件 均遵循 GNU 自由软件协议
// 
//  Copyleft 2008 JeCat.cn(http://team.JeCat.cn)
//
//
//  JCAT PHP框架 的正式全名是：Jellicle Cat PHP Framework。
//  “Jellicle Cat”出自 Andrew Lloyd Webber的音乐剧《猫》（《Prologue:Jellicle Songs for Jellicle Cats》）。
//  JCAT 是一个开源项目，它像音乐剧中的猫一样自由，你可以毫无顾忌地使用JCAT PHP框架。JCAT 由中国团队开发维护。
//  正在使用的这个版本是：0.5.0 / SVN信息: $Id: class.JCAT_FSPackage.php 1927 2009-07-08 11:46:01Z alee $
//
//
//
//  相关的链接：
//    [主页] http://jcat.JeCat.cn
//    [下载(HTTP)] http://code.google.com/p/jcat-php/downloads/list
//    [下载(svn)] svn checkout http://jcat-php.googlecode.com/svn/branches/0.4.0/Framework/ JCAT0.4
//    [在线文档] http://jcat.JeCat.cn/document
//    [社区] http://jj.jecat.cn/forum-7-1.html
//  不很相关：
//    [MP3] http://www.google.com/search?q=jellicle+songs+for+jellicle+cats+Andrew+Lloyd+Webber
//    [VCD/DVD] http://www.google.com/search?q=CAT+Andrew+Lloyd+Webber+video
//
///////////////////////////////////////////////////////////////////////////////////////////////////////
/*-- Project Introduce --*/

/**
 * 用于文件系统打包
 *
 */
class JCAT_FilePacker extends JCAT_FSPackageOperatorBase
{
	// 打包方式
	const PACK_COMPRESS_1 = 1 ;			// 000 0 0001
	const PACK_COMPRESS_2 = 2 ;			// 000 0 0010
	const PACK_COMPRESS_3 = 3 ;			// 000 0 0011
	const PACK_COMPRESS_4 = 4 ;			// 000 0 0100
	const PACK_COMPRESS_5 = 5 ;			// 000 0 0101
	const PACK_COMPRESS_6 = 6 ;			// 000 0 0110
	const PACK_COMPRESS_7 = 7 ;			// 000 0 0111
	const PACK_COMPRESS_8 = 8 ;			// 000 0 1000
	const PACK_COMPRESS_9 = 9 ;			// 000 0 1001
	const PACK_COMPRESS = self::PACK_COMPRESS_9 ;
	const PACK_COMPRESSED = 15 ;		// 000 0 1111
	
	const PACK_VERIFY = 16 ;			// 000 1 0000
		
	const PACK_DEFAULT = 0 ;			// 
	

	/**
	 * 将一个文件或整个目录打包到文件包里
	 *
	 * @access	public
	 * @param	$sPath								string		文件或目录路径
	 * @param	$sPathInPkg=null					string		在包内的路径
	 * @param	$nPackFlag=self::PACK_DEFAULT		int			打包方式
	 * @return	void
	 */
	public function Append($sPath,$sPathInPkg=null,$nPackFlag=self::PACK_DEFAULT)
	{		
		if( !file_exists($sPath) )
		{
			throw new JCAT_Exception('路径：%s 不存在',$sPath) ;
		}
		
		// 取得/创建 fso对象
		if(!$sPathInPkg)
		{
			$sPathInPkg = '/'.basename($sPath) ;
		}
		
		// 整理 path
		$arrPathInPkg = JCAT_FSPackageOperatorBase::MakePkgPath($sPathInPkg) ;
		
		$bFolder = is_dir($sPath) ;
		
		// 取得包内的 fso
		$aFSO = $this->aRootFolder->CreateChildRecursion($arrPathInPkg,$bFolder) ;

		// 目录
		if( $bFolder )
		{
			$this->PackDirTree($sPath,$aFSO,$nPackFlag) ;
		}
		
		// 文件
		else 
		{
			$aFSO->SetPath($sPath) ;
		}
	}
	
	/**
	 * Description
	 *
	 * @access	public
	 * @param	$Parameter
	 * @return	void
	 */
	public function AppendChildren($sFolderPath,$sToSubFolderPath='/',$nPackFlag=self::PACK_DEFAULT)
	{		
		// 目的目录
		if($sToSubFolderPath==='/')
		{
			$aToFolder = $this->aRootFolder ;
		}
		
		else
		{
			// 整理路径
			$arrToSubFolderPath = JCAT_FSPackageOperatorBase::MakePkgPath($sToSubFolderPath) ;
			
			// 找到目标目录
			$aToFolder = $this->aRootFolder->GetChildRecursion($arrToSubFolderPath) ;
			
			// 递归创建目标目录h
			if(!$aToFolder)
			{
				if( !is_array($arrToSubFolderPath) or !count($arrToSubFolderPath) )
				{
					throw new JCAT_Exception(JCAT_Language::SentenceEx(
						'路径错误。%s'
						, 'JCAT', null, $arrToSubFolderPath
					)) ;
				}
				$aToFolder = $this->aRootFolder->CreateChildRecursion($arrToSubFolderPath,true) ;
			}
		}
		
		// 递归扫描
		$this->PackDirTree($sFolderPath,$aToFolder,$nPackFlag) ;
	}
	
	/**
	 * Description
	 *
	 * @access	private
	 * @param	$Parameter
	 * @return	void
	 */
	private function PackDirTree($sFSPath,JCAT_FSPackageFolder $aPackIn,$nPackFlag=self::PACK_DEFAULT)
	{
		$hFolder = opendir($sFSPath) ;
		if(!$hFolder)
		{
			throw new JCAT_Exception('路径：%s 不存在',$sFSPath) ;
		}
		
		while( $sFilename=readdir($hFolder) ) 
		{
			if( in_array($sFilename,array('.','..')) )
			{
				continue ;
			}
			
			$sFilePath = $sFSPath.'/'.$sFilename ;
			
			// 使用过滤
			foreach ($this->arrPathFilters as $sFilter)
			{
				if( preg_match($sFilter,$sFilePath) )
				{
					continue 2 ;
				}
			}

			// 递归下级
			if( is_dir($sFilePath) )
			{
				$aFileFSO = $aPackIn->CreateChildRecursion(array($sFilename),true) ;
				$this->PackDirTree($sFilePath,$aFileFSO,$nPackFlag) ;
			}
			
			// 文件
			else
			{
				$aFileFSO = $aPackIn->CreateChildRecursion(array($sFilename),false) ;
				$aFileFSO->SetPackFlag($nPackFlag) ;
			}
			
			$aFileFSO->SetPath($sFilePath) ;
		}
	}
	
	
	/**
	 * Description
	 *
	 * @access	public
	 * @param	$Parameter
	 * @return	void
	 */
	public function CreateFolder($sFolderPath)
	{
		$arrPath = JCAT_FSPackageOperatorBase::MakePkgPath($sFolderPath) ;
		if( !is_array($arrPath) or !count($arrPath) )
		{
			throw new JCAT_Exception(JCAT_Language::SentenceEx(
				'路径无效。%s'
				, 'JCAT', null, $sFolderPath
			)) ;
		}
		
		return $this->aRootFolder->CreateChildRecursion($arrPath,true) ;
	}
	
	/**
	 * Description
	 *
	 * @access	public
	 * @param	$sPathInPkg		string		在包内的路径
	 * @return	void
	 */
	public function Remove($sPathInPkg)
	{		
		$this->aRootFolder->RemoveRecursion(JCAT_FSPackageOperatorBase::MakePkgPath($sPathInPkg)) ;
	}
	
	
	/**
	 * 打包
	 *
	 * @access	public
	 * @param 	$sPackTo					string	包路径
	 * @param 	$sTempDir=null				string	为打包操作提供一个临时文件目录里
	 * @return	void
	 */
	public function Pack($sPackTo,$sTempDir=null)
	{
		// 目的目录
		if(!$sTempDir)
		{
			$sTempDir = dirname($sPackTo).'/' ;
			
			if(!file_exists($sTempDir))
			{
				if(mkdir($sTempDir))
				{
					chmod($sTempDir,0777) ;
				}
			}
		}
		
		if( !is_dir($sTempDir) or !is_writable($sTempDir) )
		{
			throw new JCAT_Exception("临时文件目录不存在（也无法创建）或没有可写入权限：".$sTempDir) ;
		}
	
		// 创建压缩包
		$hPackage = fopen($sPackTo,'wb') ;
		if(!$sPackTo)
		{
			throw new JCAT_Exception("无法创建压缩包:".$sPackTo) ;
		}
		
		// 打包到临时文件
		// =====================================
		$sBodyTempPath = $sTempDir.'/'.microtime(true).rand(0,999) ;
		$hBodyTemp = fopen($sBodyTempPath,'wb') ;
		if(!$hBodyTemp)
		{
			throw new JCAT_Exception("无法创建文件：".$sBodyTempPath) ;
		}
		
		// 历遍所有文件，执行打包操作
		$bCompressPackageInfoXml = false ;
		$nSeek = 0 ;
		$aIterator = $this->CreateIterator('/',JCAT_FSPackage::ITER_RET_FSO|JCAT_FSPackage::ITER_FILE) ;
		for ( $aIterator->First(); !$aIterator->IsDone(); $aIterator->Next() )
		{
			$aFSO = $aIterator->Current() ;
			$aFSO->Pack($nSeek,$hBodyTemp,$sTempDir) ;
			$nSeek+= $aFSO->GetLength() ;
		
			// 当有一个 文件以压缩方式 被打包，则文件包头部的 xml信息 以压缩方式保存
			if(!$bCompressPackageInfoXml and $aFSO->GetCompressLevel())
			{
				$bCompressPackageInfoXml = true ;
			}
		}
		
		fclose($hBodyTemp) ;
		
		
		// 写入头文件
		// =====================================
		$sHeader = self::MakePackageSymbol() ;
		fwrite($hPackage,$sHeader) ;

		// 生成 xml 信息
		$sFileLst = $this->aRootFolder->MakeXML(true) ;
		$sExtensional = $this->MakeExtensionalXml() ;
		$sXML = "<JCAT.PACKAGE><FileLst>{$sFileLst}</FileLst><Extensional>{$sExtensional}</Extensional></JCAT.PACKAGE>" ;

		// 压缩 xml 信息
		if($bCompressPackageInfoXml)
		{
			$sXML = gzcompress($sXML,9) ;
		}
		
		$sXMLLen = strlen($sXML) ;
		fwrite($hPackage, pack("ClA{$sXMLLen}"
				, $bCompressPackageInfoXml?'1':'0', $sXMLLen, $sXML) ) ;
		
		// 合并临时包
		$hBodyTemp = fopen($sBodyTempPath,'rb') ;
		if(!$hBodyTemp)
		{
			throw new JCAT_Exception("无法读取临时包文件：".$sBodyTempPath) ;
		}
		while($sData=fread($hBodyTemp,10240))
		{
			fwrite($hPackage,$sData) ;
		}
	
		// 清理临时文件	
		// =====================================	
		fclose($hBodyTemp) ;
		fclose($hPackage) ;
		unlink($sBodyTempPath) ;
	}
	
	/**
	 * Description
	 *
	 * @access	public
	 * @static
	 * @return	string
	 */
	static public function MakePackageSymbol()
	{
		$arrPackArg = array(
			str_repeat('C',JCAT_FSPackage::PACKAGE_SYMBOL_LEN).'l'
		) ;
		
		// 开头标记
		for ($nIdx=0;$nIdx<JCAT_FSPackage::PACKAGE_SYMBOL_LEN;$nIdx++)
		{
			$arrPackArg[] = 255-ord(substr(JCAT_FSPackage::PACKAGE_SYMBOL,$nIdx,1)) ;
		}
		
		// 版本号
		$aVersion = JCAT_Version::FromString(JCAT_FSPackage::PACKAGE_VERSION) ;
		$arrPackArg[] = $aVersion->Get32Integer() ;
		
		return call_user_func_array('pack',$arrPackArg) ;
	}
	
	/**
	 * Description
	 *
	 * @access	public
	 * @param	$aFilter	string		一个正则表达式
	 * @return	void
	 */
	public function AddPathFilter($sRegexpFilter)
	{
		$this->arrPathFilters[] = $sRegexpFilter ;
	}
	
	/**
	 * Description
	 *
	 * @access	public
	 * @return	void
	 */
	public function ClearPathFilters()
	{
		$this->arrPathFilters = array() ;
	}
	
	/**
	 * Description
	 *
	 * @access	public
	 * @return	array
	 */
	public function GetPathFilters()
	{
		return $this->arrPathFilters ;
	}

	/**
	 * Description
	 *
	 * @access	protected
	 * @return	string
	 */
	protected function MakeExtensionalXml()
	{
		$sXML = '' ;
		
		foreach($this->arrExtensionalInfo as $sName=>$sContent)
		{
			$sName = addslashes($sName) ;
			$sXML.= "<Item name=\"{$sName}\"><![CDATA[{$sContent}]]></Item>" ;
		}
		
		return $sXML ;
	}

	//////////////////////////////////////////////////////////////////////////////////////////	
	
	/**
	 * Description
	 * 
	 * @access	private
	 * @var		array
	 */
	private $arrPathFilters = array() ;
}


?>